﻿using System;
using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning.GradEAAlg
{
	/// <summary>
	/// [molecule]
	/// 
	/// Singleton class to store history of individuals changes.
	/// </summary>
	[Serializable]
	public class ChangesLog
	{
		public const int HISTORY_SIZE = 1000;

		private List<ChangeData> log;
		protected static ChangesLog hLog;

		public static ChangesLog Instance
		{
			get
			{
				if (hLog != null) return hLog;
				hLog = new ChangesLog();
				return hLog;
			}
		}

		public List<ChangeData> ChangesLogData
		{
			get { return log; }
			set { log = value; }
		}

		protected ChangesLog()
		{
			log = new List<ChangeData>();
		}

		#region - Registration of changes. -
		/// <summary>
		/// [molecule]
		/// 
		/// Registers given changes.
		/// </summary>
		/// <param name="chData"></param>
		public void RegisterChanges(ChangeData chData)
		{
			// todo: think about optimization of registering changes so that information about changes at the nearby points would be stored together. That is need to optimize the storage.

			if (Contains(chData)) return;

			// compute estimate for a gradient.
			var fitChange = FitnessChange(chData.FitnessChange);
			var dist_1 = 1f / VectorMath.L2Norm(chData.Change);
			chData.GradientEstimate = VectorMath.Mul(fitChange, dist_1);

			log.Add(chData);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Registers given changes.
		/// </summary>
		public void RegisterChanges(Operator actor, Individual ind, float[] change, Fitness fitChange)
		{
			var changes = new ChangeData();
			changes.Actor = actor;
			changes.Change = change;
			changes.Individual = ind;
			changes.FitnessChange = fitChange;
			RegisterChanges(changes);
		} 
		#endregion

		#region - Search. -
		/// <summary>
		/// [molecule]
		/// 
		/// Returns changes made by the given operator.
		/// </summary>
		/// <param name="op"></param>
		/// <returns></returns>
		public List<ChangeData> SearchByOperator (Operator op)
		{
			var res = new List<ChangeData>();
			foreach (var data in log)
			{
				if (data.Actor == op)
				{
					res.Add(data);
				}
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Searches through a log to find out whether it contains given data.
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public bool Contains (ChangeData data)
		{
			foreach (var changeData in log)
			{
				if (changeData.Equals(data))
				{
					return true;
				}
			}
			return false;
		}
		#endregion

		#region - Utilities. -
		/// <summary>
		/// [molecule]
		/// 
		/// Computes numeric fitness change given fitness's delta.
		/// </summary>
		/// <param name="fit"></param>
		/// <returns></returns>
		public static float[] FitnessChange (Fitness fit)
		{
			if (fit.Extra.Count > 0)
			{
				return fit.Extra.ToArray();
			}
			return new [] {fit.Value};
		}

		public string[] ToStrings ()
		{
			var res = new string[log.Count];
			for (int i = 0; i < log.Count; i++)
			{
				res[i] = log[i].ToString();
			}
			return res;
		}
		#endregion

		#region - Save/Load. -
		/// <summary>
		/// [molecule]
		/// 
		/// Loads logs from the specified file.
		/// </summary>
		/// <param name="filename"></param>
		public void Load(string filename)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Saves logs to the specified file.
		/// </summary>
		/// <param name="filename"></param>
		public void Save(string filename)
		{
			FileIO.Serialize(filename, this);
		}
		#endregion
	}
}